﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Caching;
using System.Web.Helpers;
using System.Web.Mvc;
using Mindfor.Web.Data;
using Mindfor.Web.Mvc;
using Mindfor.Web.Routing;
using NHibernate;
using NHibernate.Linq;

namespace Mindfor.Web.Modules.Pages.Controllers
{
	/// <summary>
	/// Defines methods for manipulating pages: add, edit, delete.
	/// </summary>
	[CmsAuthorize(CmsRoles.PagesManager)]
	public class PagesController : CmsController
	{
		/// <summary>
		/// Returns ActionResult which renders index view.
		/// </summary>
		[HttpGet]
		[AdminLink("Страницы", "Сайт")]
		[UrlRoute(Path="Admin/Pages")]
		public ActionResult Index()
		{
			ViewData.Model = Data.Query<Page>().GetTreePages();
			ViewData["IsAdmin"] = User.IsInRole(CmsRoles.Administrator);
			return View();
		}

		/// <summary>
		/// Returns ActionResult to add page.
		/// </summary>
		[UrlRoute("Add", Path="Admin/Pages")]
		[UrlRouteDefault("parentId", null)]
		[UrlRoute(1, "Add/Parent{parentId}", Path="Admin/Pages")]
		[UrlRouteConstraint(1, "parentId", Constraints.IsInt)]
		[UrlSubAction("Index")]

		[HttpGet]
		[LoadModelState]
		[ReturnUrlEnable]
		public ActionResult Add(int? parentId)
		{
			return Edit(null, parentId);
		}

		/// <summary>
		/// Adds page
		/// </summary>
		[HttpPost]
		[CmsAuthorize(CmsRoles.PagesManager)]
		[ValidateInput(false)]
		public ActionResult Add(FormCollection form, string tags, string controllerAndAction, int? imageType, string returnUrl)
		{
			return Edit(null, form, tags, controllerAndAction, imageType, returnUrl);
		}

		/// <summary>
		/// Returns ActionResult to edit page.
		/// </summary>
		/// <param name="id">Page id to edit.</param>
		/// <param name="parentId">Page parent id to default selection.</param>
		[UrlRoute("{id}/Edit", Path="Admin/Pages")]
		[UrlRouteConstraint("id", Constraints.IsInt)]
		[UrlSubAction("Index")]
		
		[HttpGet]
		[LoadModelState]
		[ReturnUrlEnable]
		public ActionResult Edit(int? id, int? parentId)
		{		
			// get page
			Page page = null;
			if (id.HasValue)
			{
				page = Data.Get<Page>(id.Value);
				if (page == null)
					return HttpNotFound();
				ViewData.Model = page;
				parentId = page.Parent == null ? (int?)null : page.Parent.Id;
			}

			// create folder
			Folder folder = Data.Query<Folder>().FirstOrDefault(r => r.Name == Module.Name && r.Parent == null);
			if (folder == null)
			{
				folder = new Folder(Module.Name);
				using (ITransaction t = Data.BeginTransaction())
				{
					Data.Save(folder);
					t.Commit();
				}
			}
			
			// create data
			PageEditModel data = new PageEditModel();
			data.Pages = Data.Query<Page>().ToList();
			data.Roles = Data.Query<Role>().ToList();
			data.Languages = Data.Query<Language>().ToList();
			data.MceFolder = folder;
			ViewData["Data"] = data;
			
			if (ViewData["Language"] == null)
				ViewData["Language"] = data.Languages[0];
			
			List<ActionPath> actionPaths = new List<ActionPath>();
			foreach (ModuleBase module in ModuleFactory.Default.Modules.GetRegisteredModules())
			{
				actionPaths.AddRange(module.GetActionPaths(true, false, false));
			}
			data.Actions = actionPaths;

			// set default values
			ModelState.SetDefaultModelValue("Parent.Id", parentId);
			if (ViewData["LanguageId"] == null)
				ViewData["LanguageId"] = Language.Id;

			// return
			ViewData["IsAdmin"] = User.IsInRole(CmsRoles.Administrator);
			return View("Edit");
		}

		/// <summary>
		/// Renders partial view for roles management.
		/// </summary>
		/// <returns>EditRoles partial view.</returns>
		[UrlSubAction("Index")]
		[UrlRoute("EditRoles", Path="Admin/Pages")]
		public ActionResult EditRoles(int? id, string moduleName, string controllerName, string actionName)
		{
			// get page
			Page page = null;
			if (id.HasValue)
			{
				page = Data.Get<Page>(id.Value);
				if (page == null)
					return null;
			}

			// get action roles
			ActionPath actionPath = new ActionPath(moduleName, controllerName, actionName);
			string[] actionRoles = actionPath.GetActionAllowRoles();
			
			// get selected
			bool selectedAnonimous = true;
			bool selectedAuthorized = true;
			string[] selectedRoles = null;
			if (actionRoles != null)
			{
				selectedAnonimous = false;
				selectedRoles = actionRoles;
			}
			else if (page != null)
			{
				selectedAnonimous = page.AllowAnonymous;
				selectedAuthorized = page.AllowAuthorized;
				selectedRoles = page.AllowRoles.Select(r => r.Name).ToArray();
			}

			// render partial view
			PageEditRolesData model = new PageEditRolesData()
			{
				Roles = Data.Query<Role>().ToList(),
				SelectedAnonymous = selectedAnonimous,
				SelectedAuthorized = selectedAuthorized,
				SelectedRoles = selectedRoles,
				IsEnabled = actionRoles == null
			};
			return PartialView(model);
		}

		

		/// <summary>
		/// Edits IPage.
		/// </summary>
		[HttpPost]
		[ValidateInput(false)]
		public ActionResult Edit(int? id, FormCollection form, string tags, string controllerAndAction, int? imageType, string returnUrl)
		{
			// get page
			Page page = null;
			string pathOld = null;
			if (id.HasValue)
			{
				page = Data.Get<Page>(id.Value);
				if (page == null)
					return HttpNotFound();
				pathOld = page.GetPath();
			}
			else
			{
				page = new Page();
			}
						
			// bind values
			List<PageText> texts = new List<PageText>();
			TryUpdateModel(page, null, null, new string[] { "Tags", "Texts", "Image", "Sort" });
			TryUpdateModel(texts, "Texts");

			// check page name and path
			if (page.Name == null)
				page.Name = "";

			if (page.Parent != null && String.IsNullOrEmpty(page.Name))
				ModelState.AddModelError("name", "Путь обязателен для подстраниц");

			string nameToCheck = page.Name == null ? "" : page.Name.ToLowerInvariant();
			IQueryable<Page> pageQuery = Data.Query<Page>().Where(r => r.Parent == page.Parent && r.Name.ToLowerInvariant() == nameToCheck);
			if (id.HasValue)
				pageQuery = pageQuery.Where(r => r.Id != id.Value);
			if (pageQuery.Any())
				ModelState.AddModelError("name", "Страница с таким путем уже существует");

			if (ModelState.IsValid)
			{
				bool isAdmin = User.IsInRole(CmsRoles.Administrator);

				using (ITransaction t = Data.BeginTransaction())
				{
					// set create date
					if (id == null)
					{
						IQueryable<Page> brothers = Data.Query<Page>().Where(r => r.Parent == page.Parent);
						page.CreateDate = DateTime.UtcNow;
						page.Sort = brothers.Count() == 0 ? 0 : (brothers.Max(r => r.Sort) + 1);
					}

					// bind tags
					if (String.IsNullOrWhiteSpace(tags))
						page.Tags.Clear();
					else
					{
						// get required tags
						string[] tagsRaw = tags.Split(',');
						var tagsPair = new List<global::System.Tuple<string, string>>();
						for (int i=0; i<tagsRaw.Length; i++)
						{
							if (!String.IsNullOrWhiteSpace(tagsRaw[i]))
							{
								string tag = tagsRaw[i].Trim();
								tagsPair.Add(new global::System.Tuple<string, string>(tag, tag.ToLowerInvariant()));
							}
						}

						// remove tags
						for (int i=page.Tags.Count-1; i>=0; i--)
						{
							if (!tagsPair.Any(r => r.Item2 == page.Tags[i].NameLower))
								page.Tags.RemoveAt(i);
						}
						
						// add tags
						foreach (var tag in tagsPair)
						{
							if (!page.Tags.Any(r => r.NameLower == tag.Item2))
							{
								Tag newTag = Data.Query<Tag>().FirstOrDefault(r => r.NameLower == tag.Item2);
								if (newTag == null)
								{
									newTag = new Tag(tag.Item1);
									Data.Save(newTag);
								}
								page.Tags.Add(newTag);
							}
						}
					}
				
					// bind module, controller, action
					if (isAdmin && page.ActionPath != null && !String.IsNullOrEmpty(page.ActionPath.ModuleName))
					{
						string[] ca = controllerAndAction.Split(new string[] { " / " }, StringSplitOptions.RemoveEmptyEntries);
						if (ca.Length == 2)
						{
							page.ActionPath.ControllerName = ca[0];
							page.ActionPath.ActionName = ca[1];
						}
						else
							page.ActionPath = null;
					}
					else
						page.ActionPath = null;

					// bind image
					switch (imageType)
					{
						// insert new image
						case null:
						case 0:
							if (Request.Files["Image"] != null && Request.Files["Image"].ContentLength > 0)
								page.Image = Request.Files["Image"].GetBytes();
							break;
						// delete
						case 1:
							if (page.Image != null)
								page.Image = null;
							break;
					};

					// roles
					string[] actionRoles = page.ActionPath == null ? null : page.ActionPath.GetActionAllowRoles();
					if (page.AllowAuthorized && actionRoles == null)
					{
						// edit roles
						foreach (Role role in Data.Query<Role>().ToList())
						{
							string roleId = "Role-" + role.Id;
							bool addRole = form.EvalBool(roleId) ?? false;
							bool hasRole = page.AllowRoles.Contains(role);
							if (hasRole && !addRole)
								page.AllowRoles.Remove(role);
							else if (!hasRole && addRole)
								page.AllowRoles.Add(role);
						}
					}
					else
					{
						page.AllowRoles.Clear();
					}

					// save page
					Data.SaveOrUpdate(page);

					// save page texts
					List<PageText> textsToDelete = new List<PageText>(page.Texts);
					for (int i=0; i<texts.Count; i++)
					{
						PageText text = texts[i];
						string prefix = "Texts[" + i + "]";

						if (!ValueProvider.GetValue<bool>(prefix + ".IsDeleted"))
						{
							text.Page = page;

							// empty head title
							if (ValueProvider.GetValue<bool>(prefix + ".HeadTitleEmpty"))
								text.HeadTitle = "";

							// Apply typografy to page text
							TypografPageText(text);

							// add to page
							PageText dbText = textsToDelete.FirstOrDefault(r => r.Language == text.Language);
							if (dbText != null)
							{
								text = Data.MergeEntity(text);
								textsToDelete.Remove(dbText);
							}
							else
								page.Texts.Add(text);
							Data.SaveOrUpdate(text);
						}
					}

					foreach (PageText pt in textsToDelete)
						page.Texts.Remove(pt);
					
					// commit
					t.Commit();
				}

			    // call restart application if need to register routes
			    if (pathOld == null || page.GetPath() != pathOld)
			        CmsApplication.Restart();

			    // redirect
				return RedirectToReturnUrl(Url.Action("Index"));
			}

			// model is invalid
			this.SaveModelState();
			return RedirectToAction(id.HasValue ? "Edit" : "Add", new { returnUrl });
		}

		/// <summary>
		/// Apply typografy to page text via ArtLebedev Studio Typograf.
		/// </summary>
		/// <param name="pageText">Page text to process.</param>
		private void TypografPageText(PageText pageText)
		{
			try
			{
				pageText.Title = Mindfor.Web.Features.Typograf.ProcessText(pageText.Title, Features.TypografEntityTypes.NoEntities);
				pageText.Text = Mindfor.Web.Features.Typograf.ProcessText(pageText.Text, Features.TypografEntityTypes.HtmlEntities);
			}
			catch (Exception ex)
			{
				// if service is not available - add log and exit.
				LogError(ex);
				return;
			}
		}

		/// <summary>
		/// Deletes page.
		/// </summary>
		/// <param name="id">Page id to delete.</param>
		/// <returns></returns>
		[UrlSubAction("Index")]
		[UrlRoute("{id}/Delete", Path="Admin/Pages")]
		[UrlRouteConstraint("id", Constraints.IsInt)]
		public ActionResult Delete(int id)
		{
			// get page
			Page page = Data.Get<Page>(id);
			if (page == null)
				return HttpNotFound();

			// delete
			using (ITransaction t = Data.BeginTransaction())
			{
				DeleteChildPages(page);
				Data.Delete(page);
				t.Commit();
			}
			
			// redirect
			return RedirectToReturnUrl(Url.Action("Index"));
		}

		/// <summary>
		/// Deletes child pages from database recursively.
		/// </summary>
		/// <param name="page">Page which children are to be deleted.</param>
		private void DeleteChildPages(Page page)
		{
			if (page.ChildPages.Count > 0)
			{
				foreach (Page child in page.ChildPages)
				{
					DeleteChildPages(child);
					Data.Delete(child);
				}
			}	
		}

		/// <summary>
		/// Moves page up in pages list. Redirects back to pages list.
		/// </summary>
		/// <param name="id">Page id.</param>
		[UrlSubAction("Index")]
		[UrlRoute("{id}/MoveUp", Path="Admin/Pages")]
		[UrlRouteConstraint("id", Constraints.IsInt)]
		public ActionResult MoveUp(int id)
		{
			Page page = Data.Get<Page>(id);
			if (page == null)
				return HttpNotFound();

			using (ITransaction t = Data.BeginTransaction())
			{
				foreach (Page p in Data.Query<Page>().MovePageUp(page))
					Data.Update(p);
				t.Commit();
			}

			// redirect
			return RedirectToReturnUrl(Url.Action("Index"));
		}

		/// <summary>
		/// Moves page down in pages list. Redirects back to pages list.
		/// </summary>
		/// <param name="id">Page id.</param>
		[UrlSubAction("Index")]
		[UrlRoute("{id}/MoveDown", Path="Admin/Pages")]
		[UrlRouteConstraint("id", Constraints.IsInt)]
		public ActionResult MoveDown(int id)
		{
			Page page = Data.Get<Page>(id);
			if (page == null)
				return HttpNotFound();

			using (ITransaction t = Data.BeginTransaction())
			{
				foreach (Page p in Data.Query<Page>().MovePageDown(page))
					Data.Update(p);
				t.Commit();
			}

			// redirect
			return RedirectToReturnUrl(Url.Action("Index"));
		}
	}
}
